package com.zeny.express.framework.core.support;

import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.validation.MessageInterpolator;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;

import com.zeny.express.framework.shared.domain.Constants;

/**
 * JSR-303 BEAN VALIDATION 自定义国际化实现
 * 
 * @author zhuwei
 * @date 2013年9月17日 上午11:41:57
 */
public class LocaleMessageInterpolator implements MessageInterpolator {

	/**
	 * spring message source
	 */
	@Autowired
	private MessageSource messageSource;

	/**
	 * default locale
	 */
	private Locale defaultLocale = Constants.DEFAULT_LOCALE;

	/** Regular expression used to do message interpolation. */
	private static final Pattern messageParameterPattern = Pattern
			.compile("(\\{[\\w\\.]+\\})");

	@Override
	public String interpolate(String message, Context context) {
		return interpolate(message, context, defaultLocale);
	}

	@Override
	public String interpolate(String message, Context context, Locale locale) {
		return interpolateMessage(message, context.getConstraintDescriptor()
				.getAttributes(), locale);
	}

	/**
	 * Runs the message interpolation according to algorithm specified in JSR
	 * 303. <br/>
	 * Note: <br/>
	 * Lookups in user bundles are recursive whereas lookups in default bundle
	 * are not!
	 * 
	 * @param message
	 *            the message to interpolate
	 * @param annotationParameters
	 *            the parameters of the annotation for which to interpolate this
	 *            message
	 * @param locale
	 *            the <code>Locale</code> to use for the resource bundle.
	 * @return the interpolated message.
	 */
	private String interpolateMessage(String message,
			Map<String, Object> annotationParameters, Locale locale) {

		String userBundleResolvedMessage;
		String resolvedMessage = message;
		boolean evaluatedDefaultBundleOnce = false;
		do {
			// search the user bundle recursive (step1)
			userBundleResolvedMessage = replaceVariables(resolvedMessage,
					locale, true);

			// exit condition - we have at least tried to validate against the
			// default bundle and there were no
			// further replacements
			if (evaluatedDefaultBundleOnce
					&& userBundleResolvedMessage.equals(resolvedMessage)) {
				break;
			}

			// search the default bundle non recursive (step2)
			resolvedMessage = replaceVariables(userBundleResolvedMessage,
					locale, false);

			evaluatedDefaultBundleOnce = true;
		} while (true);

		// resolve annotation attributes (step 4)
		resolvedMessage = replaceAnnotationAttributes(
				userBundleResolvedMessage, annotationParameters);

		// curly braces need to be scaped in the original msg, so unescape them
		resolvedMessage = resolvedMessage.replace("\\{", "{")
				.replace("\\}", "}").replace("\\\\", "\\");

		return resolvedMessage;
	}

	private String replaceVariables(String message, Locale locale,
			boolean recurse) {
		Matcher matcher = messageParameterPattern.matcher(message);
		StringBuffer sb = new StringBuffer(64);
		String resolvedParameterValue;
		while (matcher.find()) {
			String parameter = matcher.group(1);
			resolvedParameterValue = resolveParameter(parameter, locale,
					recurse);

			matcher.appendReplacement(sb,
					sanitizeForAppendReplacement(resolvedParameterValue));
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	private String replaceAnnotationAttributes(String message,
			Map<String, Object> annotationParameters) {
		Matcher matcher = messageParameterPattern.matcher(message);
		StringBuffer sb = new StringBuffer(64);
		while (matcher.find()) {
			String resolvedParameterValue;
			String parameter = matcher.group(1);
			Object variable = annotationParameters
					.get(removeCurlyBrace(parameter));
			if (variable != null) {
				if (variable.getClass().isArray()) {
					resolvedParameterValue = ArrayUtils.toString(variable);
				} else {
					resolvedParameterValue = variable.toString();
				}
			} else {
				resolvedParameterValue = parameter;
			}
			matcher.appendReplacement(sb,
					sanitizeForAppendReplacement(resolvedParameterValue));
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	private String resolveParameter(String parameterName, Locale locale,
			boolean recurse) {

		String parameterValue = messageSource.getMessage(
				removeCurlyBrace(parameterName), null, parameterName, locale);

		if (recurse && !parameterName.equals(parameterName)) {
			parameterValue = replaceVariables(parameterValue, locale, recurse);
		}

		return parameterValue;
	}

	private String removeCurlyBrace(String parameter) {
		return parameter.substring(1, parameter.length() - 1);
	}

	/**
	 * Escapes the string to comply with
	 * {@link Matcher#appendReplacement(StringBuffer, String)} requirements.
	 * 
	 * @param src
	 *            The original string.
	 * @return The sanitized string.
	 */
	private String sanitizeForAppendReplacement(String src) {
		return src.replace("\\", "\\\\").replace("$", "\\$");
	}

}
